﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using BeanboxSoftware.BeanProxy.Internals;

namespace BeanboxSoftware.BeanProxy.Recording
{
  public class ExpectedCall
  {
    protected MethodConfig _methodConfig;
    protected ExpectedReturn _expectedReturn;
    protected In _type;
    protected object[] _expectedArgs;

    public virtual ExpectedReturn For(params object[] args)
    {
      if (args == null)
      {
        args = new object[] { null };
      }

      _expectedArgs = args;
      return _expectedReturn;
    }

    protected ExpectedCall()
    {
      _expectedReturn = new ExpectedReturnAccess((ExpectedCallAccess)this);
    }
  }

  public class ExpectedCallAccess : ExpectedCall
  {
    public ExpectedCallAccess(MethodConfig mc, In recordType)
    {
      _methodConfig = mc;
      _type = recordType;
    }

    public MethodConfig MethodConfig { get { return _methodConfig; } }
    public ExpectedReturnAccess ExpectedReturn { get { return (ExpectedReturnAccess)_expectedReturn; } set { _expectedReturn = value; } }
    public object[] ExpectedArgs { get { return _expectedArgs; } set { _expectedArgs = value; } }
    public string ExpectedArgsList { get { return string.Join(", ", _expectedArgs.Select(o => o == null ? "[null]" : o.ToString()).ToArray()); } }
    public In RecordType { get { return _type; } }
  }

  public interface IForExposer<C>
  {
  }

  internal class ForBuilder<C> : Builder<IForExposer<C>>
  {
    public override IEnumerable<MethodInfo> AllMethods(bool includeProps)
    {
      yield return MethodConfig.GetInvoker(typeof(C));
    }

    public override string BuildOverride(MethodInfo mi)
    {
      return Virtual;
    }

    public override string BuildIfReal(MethodInfo method, string action)
    {
      return "";
    }
  }

  public class ExpectedCall<C>
  {
    protected ExpectedCallAccess _access;
    protected IForExposer<C> _exposer;

    public ExpectedCall(ExpectedCallAccess access)
    {
      _access = access;

      var builder = new ForBuilder<C>();
      _exposer = builder.CreateProxy();
      var invokeConfig = ProxyManager.LookupMethod<C>(_exposer, "Invoke");
      invokeConfig.Access.Add(va =>
      {
        var then = _access.ExpectedReturn.Then;
        _access.For(va).Then = then;

        Type t = invokeConfig.Access.Method.ReturnType;

        return t == typeof(void) ? null : BuilderBase.Default(t);
      });

      var del = Delegate.CreateDelegate(typeof(C), _exposer, 
        invokeConfig.Access.Method, false);
      For = (C)((object)del);
    }

    public ExpectedCallNoExecture<C> AndRun(Func<object[], object> callback)
    {
      _access.ExpectedReturn.Then = callback;
      return new ExpectedCallNoExecture<C>(this);
    }

    public C For
    {
      get;
      protected set;
    }
  }

  public class ExpectedCallNoExecture<C>
  {
    protected ExpectedCall<C> _access;

    public ExpectedCallNoExecture(ExpectedCall<C> access)
    {
      _access = access;
    }

    public C For
    {
      get { return _access.For; }
    }
  }
}
